package com.itheima.mm.security;

import com.itheima.framework.RequestMapping;
import com.itheima.framework.utils.ClassScannerUtils;
import com.itheima.mm.constants.Constants;
import com.itheima.mm.pojo.User;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 包名:com.itheima.mm.security
 * 作者:Leevi
 * 日期2020-02-07  14:13
 */
public class SecurityFilter implements Filter{
    private Map<String,String> accessPathMap = new HashMap<>();//这个map就是存放:路径和"角色/权限的对应关系"的容器
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //读取xml配置文件，能够获取到"哪个页面需要哪些角色关键字"
        SAXReader reader = new SAXReader();
        //将要读取的xml文件转换成流
        //获取要加载的配置文件的名字
        String configLocation = filterConfig.getInitParameter("configLocation");
        InputStream is = SecurityFilter.class.getClassLoader().getResourceAsStream(configLocation);
        try {
            Document document = reader.read(is);

            //读取配置文件中的信息
            //1. 获取所有的security标签
            List<Element> list = document.selectNodes("//security");
            if (list != null && list.size() > 0) {
                //2. 遍历出每一个security标签，获取pattern属性和has_role属性的值
                for (Element element : list) {
                    String pattern = element.attributeValue("pattern");//访问路径
                    String has_role = element.attributeValue("has_role");//获取角色关键字

                    //以路径作为key,以角色/权限信息作为value
                    accessPathMap.put(pattern,has_role);
                }
            }

            //解析注解
            //获取所有包含PreAuthorize的方法
            //1. 使用heimaMvc中的ClassScannerUtils工具类进行包扫描，获取所有Controller类的字节码对象
            //通过获取解析的配置文件中的scan标签的package属性，我们就能得到要进行扫描的包名
            Element element = (Element) document.selectSingleNode("//scan");
            String packageName = element.attributeValue("package");
            List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageName);

            //遍历出每一个类的字节码对象
            if (classList != null && classList.size() > 0) {
                for (Class<?> clazz : classList) {
                   //获取每一个Controller类中的所有方法
                    Method[] methods = clazz.getMethods();
                    //遍历出每一个方法
                    for (Method method : methods) {
                        //判断方法上是否有PreAuthorize注解
                        if (method.isAnnotationPresent(PreAuthorize.class)) {
                            //当前方法上有PreAuthorize这个注解
                            //1. 获取PreAuthorize注解的值
                            String value = method.getAnnotation(PreAuthorize.class).value();//这就是权限关键字

                            //2. 获取RequestMapping注解的值
                            String requestMapping = method.getAnnotation(RequestMapping.class).value();//这就是路径

                            //3. 以路径作为key，以权限关键字作为value，存储到map中
                            accessPathMap.put(requestMapping,value);
                        }
                    }
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //判断用户每次请求的路径，有没有具体的角色/权限关键字，如果有则放行，如果没有则拦截
        //授权其实就是判断:用户是否有访问该路径时候必须具备的"角色"/"权限关键字"

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute(Constants.MM_USER_USERNAME);

        //2. 获取访问当前页面/资源，需要哪些"角色"/"权限"关键字
        //2.1 获取当前访问的资源路径
        String requestURI = request.getRequestURI();// "/项目名/pages/index.html"
        //2.2 将访问路径的"/项目名"去掉
        String contextPath = request.getContextPath();//获取那个"/项目名"
        String accessPath = requestURI.substring(contextPath.length());//去掉访问路径上的"/项目名"

        //如果客户端的访问路径有".do",则去掉".do"
        if (accessPath.contains(".do")) {
            //怎么才能去掉".do",如果有".do",那么它必定在最后面
            accessPath = accessPath.replace(".do","");
        }

        //2.3 以accessPath作为key到accessPathMap中获取值，获取到的值就是访问该资源需要的"角色/权限关键字"
        //要先判断，访问的资源需不需要权限控制，如果不需要则直接放行
        if (!accessPathMap.containsKey(accessPath)) {
            //说明当前访问的资源，不需要权限控制,我们就直接放行
            filterChain.doFilter(request,response);
            return;
        }
        String authority = accessPathMap.get(accessPath);//获取访问当前资源需要的"角色/权限信息",这个里面可能包含多个关键字
        //根据","切割字符串，获取具体的每一个关键字
        String[] authorArr = authority.split(",");


        //如果需要权限控制，则先判断用户是否登录，如果用户未登录，则直接跳转到登录页面，让用户先登录
        if (user == null) {
            //说明用户未登录
            //则直接跳转到登录页面
            response.sendRedirect(contextPath+"/login.html");//重定向跳转到登录页面
            return;
        }
        //说明用户已经登录了
        // 获取用户所具备的所有"角色/权限"关键字
        List<String> authorityList = user.getAuthorityList();//这就是用户具备的所有角色、权限的关键字集合


        //声明一个boolean类型的变量，表示用户是否具备权限
        boolean flag = false;
        //接下来，遍历出每一个关键字
        //3. 对比一下，1和2的东西
        for (String keyword : authorArr) {
            //判断用户是否具备这个keyword
            if (authorityList.contains(keyword)) {
                //如果包含关键字，那么说明该用户具备访问页面的权限
                flag = true;
                break;
            }
        }

        if (flag) {
            //表示用户具备权限，可以访问页面
            //放行
            filterChain.doFilter(request,response);
            return;
        }else {
            //判断，如果请求是同步请求，则响应"当前用户权限不足，请切换用户！"
            if (accessPath.contains(".html")) {
                //表示用户不具备权限，不能访问页面
                response.getWriter().print("当前用户权限不足，请切换用户！");//直接往客户端输出一句话
            }else {
                //它肯定是异步请求
                response.getWriter().print(0);//如果是异步请求，则直接向客户端响应数据为0
            }
        }
    }

    @Override
    public void destroy() {

    }
}
